"""
Referer检查中间件
防止跨站请求伪造(CSRF)和热链接攻击，提升API安全性
"""

import time
from typing import List, Set, Optional
from urllib.parse import urlparse
from fastapi import Request, status
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware

from bot_api_v1.app.core.config import settings
from bot_api_v1.app.core.logger import logger


class RefererCheckMiddleware(BaseHTTPMiddleware):
    """
    Referer检查中间件
    
    功能特性：
    - 验证请求来源的合法性
    - 防止CSRF攻击和恶意热链接
    - 支持白名单域名配置
    - 区分静态资源和API请求的处理策略
    - 支持开发环境的灵活配置
    """
    
    def __init__(self, app):
        super().__init__(app)
        
        # 配置参数
        self.config = {
            # 基础配置
            'enabled': getattr(settings, 'REFERER_CHECK_ENABLED', True),
            'strict_mode': getattr(settings, 'REFERER_CHECK_STRICT_MODE', False),
            'allow_empty_referer': getattr(settings, 'ALLOW_EMPTY_REFERER', True),
            
            # 白名单域名（允许的来源域名）
            'allowed_domains': self._load_allowed_domains(),
            
            # 需要检查的路径模式
            'check_patterns': [
                '/api/',           # 所有API请求
                '/media/',         # 媒体处理请求
                '/wechat/',        # 微信相关请求
            ],
            
            # 豁免检查的路径（通常是静态资源）
            'exempt_patterns': [
                '/static/',        # 静态文件
                '/docs',           # API文档
                '/redoc',          # ReDoc文档
                '/openapi.json',   # OpenAPI规范
                '/monitoring/health',  # 健康检查
                '/robots.txt',     # SEO文件
                '/sitemap.xml',    # 站点地图
                '/favicon.ico',    # 图标
            ],
            
            # HTTP方法配置
            'check_methods': {'POST', 'PUT', 'PATCH', 'DELETE'},  # 需要检查的HTTP方法
            'exempt_methods': {'GET', 'HEAD', 'OPTIONS'},         # 豁免的HTTP方法
        }
        
        logger.info(f"RefererCheckMiddleware initialized with {len(self.config['allowed_domains'])} allowed domains")
    
    def _load_allowed_domains(self) -> Set[str]:
        """加载允许的域名列表"""
        domains = set()
        
        # 从配置文件加载
        config_domains = getattr(settings, 'ALLOWED_REFERER_DOMAINS', [])
        if isinstance(config_domains, (list, tuple)):
            domains.update(config_domains)
        
        # 添加项目自身的域名
        project_domains = [
            settings.DOMAIN_MAIN_URL,
            settings.DOMAIN_API_URL,
            settings.H5_FRONTEND_URL,
        ]
        
        for domain_url in project_domains:
            if domain_url:
                parsed = urlparse(domain_url)
                if parsed.netloc:
                    domains.add(parsed.netloc)
                    # 同时添加不带端口的域名
                    hostname = parsed.hostname
                    if hostname and hostname != parsed.netloc:
                        domains.add(hostname)
        
        # 开发环境默认域名
        if settings.ENVIRONMENT == "development":
            dev_domains = [
                'localhost',
                '127.0.0.1',
                'localhost:8000',
                '127.0.0.1:8000',
                'localhost:3000',   # React开发服务器
                'localhost:5173',   # Vite开发服务器
                'localhost:8083',   # 当前开发服务器端口
                '127.0.0.1:8083',   # 当前开发服务器端口
            ]
            domains.update(dev_domains)
        
        # 移除空值
        domains.discard('')
        domains.discard(None)
        
        return domains
    
    async def dispatch(self, request: Request, call_next):
        """主要的请求处理逻辑"""
        
        # 检查中间件是否启用
        if not self.config['enabled']:
            return await call_next(request)
        
        # 获取请求信息
        path = request.url.path
        method = request.method.upper()
        
        # 检查是否需要进行Referer验证
        if not self._should_check_referer(path, method):
            return await call_next(request)
        
        # 获取Referer头
        referer = request.headers.get('Referer') or request.headers.get('Referrer')
        
        # 检查Referer
        check_result = self._validate_referer(referer, request)
        
        if not check_result['valid']:
            # 记录违规事件
            await self._log_referer_violation(request, referer, check_result)
            
            # 返回拒绝响应
            return self._create_denied_response(check_result['reason'])
        
        # Referer检查通过，继续处理请求
        return await call_next(request)
    
    def _should_check_referer(self, path: str, method: str) -> bool:
        """判断是否需要检查Referer"""
        
        # 检查HTTP方法
        if method in self.config['exempt_methods']:
            return False
        
        if self.config['strict_mode'] and method not in self.config['check_methods']:
            return False
        
        # 检查豁免路径
        for pattern in self.config['exempt_patterns']:
            if path.startswith(pattern):
                return False
        
        # 检查需要验证的路径
        if self.config['check_patterns']:
            return any(path.startswith(pattern) for pattern in self.config['check_patterns'])
        
        # 默认需要检查
        return True
    
    def _validate_referer(self, referer: Optional[str], request: Request) -> dict:
        """验证Referer头的有效性"""
        
        # 处理空Referer
        if not referer:
            if self.config['allow_empty_referer']:
                return {
                    'valid': True,
                    'reason': 'empty_referer_allowed'
                }
            else:
                return {
                    'valid': False,
                    'reason': 'empty_referer_denied',
                    'message': 'Missing Referer header'
                }
        
        try:
            # 解析Referer URL
            parsed_referer = urlparse(referer)
            referer_domain = parsed_referer.netloc.lower()
            
            if not referer_domain:
                return {
                    'valid': False,
                    'reason': 'invalid_referer_format',
                    'message': f'Invalid Referer format: {referer}'
                }
            
            # 检查是否在允许的域名列表中
            if referer_domain in self.config['allowed_domains']:
                return {
                    'valid': True,
                    'reason': 'domain_whitelisted',
                    'referer_domain': referer_domain
                }
            
            # 检查是否是允许域名的子域名
            for allowed_domain in self.config['allowed_domains']:
                if referer_domain.endswith(f'.{allowed_domain}'):
                    return {
                        'valid': True,
                        'reason': 'subdomain_allowed',
                        'referer_domain': referer_domain,
                        'parent_domain': allowed_domain
                    }
            
            # Referer域名不在白名单中
            return {
                'valid': False,
                'reason': 'domain_not_allowed',
                'message': f'Referer domain not allowed: {referer_domain}',
                'referer_domain': referer_domain
            }
            
        except Exception as e:
            return {
                'valid': False,
                'reason': 'referer_parse_error',
                'message': f'Error parsing Referer: {str(e)}',
                'error': str(e)
            }
    
    async def _log_referer_violation(self, request: Request, referer: Optional[str], check_result: dict):
        """记录Referer违规事件"""
        
        client_ip = self._get_client_ip(request)
        
        # 根据违规严重程度决定日志级别
        if check_result['reason'] in ['domain_not_allowed', 'referer_parse_error']:
            # 可能的恶意请求，使用error级别
            logger.error(
                f"Referer检查失败: {check_result['reason']} - IP: {client_ip}, Path: {request.url.path}, Referer: {referer}",
                extra={
                    "event_type": "referer_check_failed",
                    "client_ip": client_ip,
                    "path": str(request.url.path),
                    "method": request.method,
                    "referer": referer,
                    "reason": check_result['reason'],
                    "message": check_result.get('message'),
                    "user_agent": request.headers.get('User-Agent'),
                    "severity": "high"
                }
            )
        else:
            # 一般违规，使用info_to_db
            logger.info_to_db(
                f"Referer检查警告: {check_result['reason']} - IP: {client_ip}, Path: {request.url.path}",
                extra={
                    "event_type": "referer_check_warning",
                    "client_ip": client_ip,
                    "path": str(request.url.path),
                    "method": request.method,
                    "referer": referer,
                    "reason": check_result['reason'],
                    "message": check_result.get('message'),
                    "user_agent": request.headers.get('User-Agent'),
                    "severity": "medium"
                }
            )
    
    def _get_client_ip(self, request: Request) -> str:
        """获取真实客户端IP"""
        forwarded_ips = [
            request.headers.get("X-Forwarded-For"),
            request.headers.get("X-Real-IP"),
            request.headers.get("CF-Connecting-IP"),
            request.headers.get("X-Client-IP"),
        ]
        
        for ip in forwarded_ips:
            if ip:
                return ip.split(',')[0].strip()
        
        return request.client.host if request.client else "unknown"
    
    def _create_denied_response(self, reason: str) -> JSONResponse:
        """创建拒绝访问响应"""
        
        # 根据不同原因返回不同的错误信息
        error_messages = {
            'empty_referer_denied': 'Access denied: Missing Referer header',
            'invalid_referer_format': 'Access denied: Invalid Referer format',
            'domain_not_allowed': 'Access denied: Unauthorized access from external domain',
            'referer_parse_error': 'Access denied: Referer validation failed'
        }
        
        message = error_messages.get(reason, 'Access denied: Referer check failed')
        
        return JSONResponse(
            status_code=status.HTTP_403_FORBIDDEN,
            content={
                "code": 403,
                "message": message,
                "data": {
                    "reason": reason,
                    "timestamp": int(time.time()),
                    "hint": "请确保从允许的域名访问此API"
                }
            },
            headers={
                "X-Content-Type-Options": "nosniff",
                "X-Frame-Options": "DENY"
            }
        )
    
    def get_configuration_info(self) -> dict:
        """获取中间件配置信息（用于调试和监控）"""
        return {
            "enabled": self.config['enabled'],
            "strict_mode": self.config['strict_mode'],
            "allow_empty_referer": self.config['allow_empty_referer'],
            "allowed_domains_count": len(self.config['allowed_domains']),
            "allowed_domains": list(self.config['allowed_domains']),
            "check_patterns": self.config['check_patterns'],
            "exempt_patterns": self.config['exempt_patterns'],
            "check_methods": list(self.config['check_methods']),
            "exempt_methods": list(self.config['exempt_methods']),
        }